/*
 * Scrollable.java
 *
 * Created on 2004/12/07 +0900
 *
 * $Revision: 59 $
 * $Date: 2008-12-28 19:22:21 +0000 (Sun, 28 Dec 2008) $
 */

package org.gikolet.framework.ui;

/**
 * @author Tetsutaro (latest modification by $Author: tetsutaro.ss $)
 * @version $Revision: 59 $ $Date: 2008-12-28 19:22:21 +0000 (Sun, 28 Dec 2008) $
 */
public abstract class Scrollable extends Widget {
    public static final int SCROLLBAR_AS_NEEDED = 0;
    public static final int SCROLLBAR_NEVER = 1;
    public static final int SCROLLBAR_ALWAYS = 2;

    private static final int SCROLLBAR_AREA_WIDTH = 6;

    private int vsbPolicy;
    private int hsbPolicy;

    private boolean showVerticalScrollBar;
    private boolean showHorizontalScrollBar;

    private int viewContentX;
    private int viewContentY;
    private int viewContentWidth;
    private int viewContentHeight;

    private int viewAreaTop;
    private int viewAreaBottom;
    private int viewAreaLeft;
    private int viewAreaRight;

    private int scrollWeight;

    protected Scrollable(boolean focusable) {
	this(focusable, SCROLLBAR_AS_NEEDED, SCROLLBAR_AS_NEEDED);
    }

    protected Scrollable(boolean focusable, int vsbPolicy, int hsbPolicy) {
	super(focusable);

	this.showVerticalScrollBar = false;
	this.showHorizontalScrollBar = false;

	setScrollWeight(-1);

	setVerticalScrollBarPolicy(vsbPolicy);
	setHorizontalScrollBarPolicy(hsbPolicy);

	setBackColor(Color.WHITE);
	setForeColor(Color.BLACK);
    }

    public void setScrollWeight(int scrollWeight) {
	if (scrollWeight <= 0) {
	    scrollWeight = -1;
	}
	this.scrollWeight = scrollWeight;
    }

    public int getScrollWeight() {
	return this.scrollWeight;
    }

    public boolean isShowVerticalScrollBar() {
	return this.showVerticalScrollBar;
    }

    public boolean isShowHorizontalScrollBar() {
	return this.showHorizontalScrollBar;
    }

    private static final Color COLOR_SCROLL_BAR = new Color(0xFF8040);
    private static final Color COLOR_SCROLL_BAR_SHADOW = new Color(0x804000);
    private static final Color COLOR_SCROLL_BAR_BACKGROUND = new Color(0x969696);

    protected void paintWidget(Graphics g) {
	int width = getWidth()
		- ((this.showVerticalScrollBar) ? SCROLLBAR_AREA_WIDTH : 0);
	int height = getHeight()
		- ((this.showHorizontalScrollBar) ? SCROLLBAR_AREA_WIDTH : 0);

	int cw = width - 1;
	int ch = height - 1;

	if (!this.showVerticalScrollBar) {
	    cw--;
	}
	if (!this.showHorizontalScrollBar) {
	    ch--;
	}

	int clipX = g.getClipX();
	int clipY = g.getClipY();
	int clipWidth = g.getClipWidth();
	int clipHeight = g.getClipHeight();

	g.clipRect(1, 1, cw, ch);
	Graphics viewContentGraphics = g.createGraphics(-getViewContentX() + 1,
		-getViewContentY() + 1, getViewContentWidth(),
		getViewContentHeight());
	paintViewContent(viewContentGraphics);
	g.setClip(clipX, clipY, clipWidth, clipHeight);

	if (this.showVerticalScrollBar) {
	    g.setColor(Color.BLACK);
	    g.drawRect(width, 0, SCROLLBAR_AREA_WIDTH - 1, height - 1);
	    g.setColor(COLOR_SCROLL_BAR_BACKGROUND);
	    g.drawRect(width + 1, 1, SCROLLBAR_AREA_WIDTH - 3, height - 3);

	    int contentHeight = getViewContentHeight();
	    if (contentHeight > 0) {
		int bh = (height * ch) / contentHeight;
		bh = (bh < 8) ? 8 : bh;

		int x = width + 1;
		int y = ((height - bh) * getViewContentY())
			/ (contentHeight - ch);

		g.setColor(COLOR_SCROLL_BAR);
		g.fillRect(x, y + 1, SCROLLBAR_AREA_WIDTH - 2, bh - 2);

		g.setColor(Color.BLACK);
		g.drawLine(x, y, x + SCROLLBAR_AREA_WIDTH - 3, y);
		g.drawLine(x, y + bh - 1, x + SCROLLBAR_AREA_WIDTH - 3, y + bh
			- 1);

		g.setColor(COLOR_SCROLL_BAR_SHADOW);
		g.drawRect(x, y + 1, SCROLLBAR_AREA_WIDTH - 3, bh - 3);
	    }
	}
	if (this.showHorizontalScrollBar) {
	    g.setColor(Color.BLACK);
	    g.drawRect(0, height, width - 1, SCROLLBAR_AREA_WIDTH - 1);
	    g.setColor(COLOR_SCROLL_BAR_BACKGROUND);
	    g.drawRect(1, height + 1, width - 3, SCROLLBAR_AREA_WIDTH - 3);

	    int contentWidth = getViewContentWidth();
	    if (contentWidth > 0) {
		int bw = (width * cw) / contentWidth;
		bw = (bw < 10) ? 10 : bw;

		int x = ((width - bw) * getViewContentX())
			/ (contentWidth - cw);
		int y = height + 1;

		g.setColor(COLOR_SCROLL_BAR);
		g.fillRect(x + 1, y, bw - 2, SCROLLBAR_AREA_WIDTH - 2);

		g.setColor(Color.BLACK);
		g.drawLine(x, y, x, y + SCROLLBAR_AREA_WIDTH - 3);
		g.drawLine(x + bw - 1, y, x + bw - 1, y + SCROLLBAR_AREA_WIDTH
			- 3);

		g.setColor(COLOR_SCROLL_BAR_SHADOW);
		g.drawRect(x + 1, y, bw - 3, SCROLLBAR_AREA_WIDTH - 3);
	    }
	}
    }

    protected void paintViewContent(Graphics g) {
    }

    protected Dimension getContentPreferredSize(int hintWidth, int hintHeight) {
	int viewContentWidth = getViewContentWidth();
	int viewContentHeight = getViewContentHeight();
	if (hintWidth != DEFAULT_LAYOUT) {
	    viewContentWidth = hintWidth;
	}
	if (hintHeight != DEFAULT_LAYOUT) {
	    viewContentHeight = hintHeight;
	}
	return new Dimension(viewContentWidth, viewContentHeight);
    }

    protected void revalidateViewContent() {
	Display display = getDisplay();
	if (display != null) {
	    final int thisCount = ++this.revalidateCount;

	    display.invoke(new Runnable() {
		public void run() {
		    if (thisCount == Scrollable.this.revalidateCount) {
			validateViewContent();
		    }
		}
	    });
	}
    }

    public void repaintViewContent() {
	repaintViewContent(0, 0, getViewContentWidth(), getViewAreaHeight());
    }

    public void repaintViewContent(int x, int y, int width, int height) {
	int vAX = getViewAreaY();
	int vAY = getViewAreaY();

	int vCX = getViewContentX();
	int vCY = getViewContentY();
	int vAW = getViewAreaWidth();
	int vAH = getViewAreaHeight();

	if (width <= 0 || height <= 0 || x + width <= 0 || vCX + vAW <= x
		|| y + height <= 0 || vCY + vAH <= y) {
	    return;
	}
	int pLeft = Math.max(x, vCX);
	int pTop = Math.max(y, vCY);
	int pRight = Math.min(x + width, vCX + vAW);
	int pBottom = Math.min(y + height, vCY + vAH);

	if (pRight - pLeft > 0 || pBottom - pTop > 0) {
	    repaint(pLeft + vAX - vCX, pTop + vAY - vCY, pRight - pLeft,
		    pBottom - pTop);
	}

    }

    private int revalidateCount = 0;

    public void validateViewContent() {
	revalidateCount = 0;

	boolean hspNever = getHorizontalScrollBarPolicy() == SCROLLBAR_NEVER;
	boolean vspNever = getVerticalScrollBarPolicy() == SCROLLBAR_NEVER;

	int width = getWidth();
	int height = getHeight();
	int w = width;
	int h = height;
	width -= 2;
	height -= 2;

	Dimension pd = getContentPreferredSize((hspNever) ? width
		: DEFAULT_LAYOUT, (vspNever) ? height : DEFAULT_LAYOUT);

	this.showVerticalScrollBar = false;
	this.showHorizontalScrollBar = false;

	if (this.vsbPolicy == SCROLLBAR_ALWAYS) {
	    this.showVerticalScrollBar = true;
	    width = w - 1 - SCROLLBAR_AREA_WIDTH;
	} else if (this.vsbPolicy == SCROLLBAR_AS_NEEDED) {
	    if (pd.getHeight() > height) {
		this.showVerticalScrollBar = true;
		width = w - 1 - SCROLLBAR_AREA_WIDTH;
	    }
	}
	if (this.hsbPolicy == SCROLLBAR_ALWAYS) {
	    this.showHorizontalScrollBar = true;
	    height = h - 1 - SCROLLBAR_AREA_WIDTH;
	} else if (this.hsbPolicy == SCROLLBAR_AS_NEEDED) {
	    if (pd.getWidth() > width) {
		this.showHorizontalScrollBar = true;
		height = h - 1 - SCROLLBAR_AREA_WIDTH;

		if (this.vsbPolicy == SCROLLBAR_AS_NEEDED) {
		    if (pd.getHeight() > height) {
			this.showVerticalScrollBar = true;
			width = w - 1 - SCROLLBAR_AREA_WIDTH;
		    }
		}
	    }
	}

	if (this.vsbPolicy == SCROLLBAR_AS_NEEDED && this.showVerticalScrollBar
		&& hspNever) {
	    pd = getContentPreferredSize(width, DEFAULT_LAYOUT);
	    this.showVerticalScrollBar = pd.getHeight() > h - 2;
	} else if (this.hsbPolicy == SCROLLBAR_AS_NEEDED
		&& this.showHorizontalScrollBar && vspNever) {
	    pd = getContentPreferredSize(DEFAULT_LAYOUT, height);
	    this.showHorizontalScrollBar = pd.getWidth() > w - 2;
	}
	int rightInset = 1;
	int bottomInset = 1;
	if (this.showVerticalScrollBar) {
	    rightInset = SCROLLBAR_AREA_WIDTH;
	}
	if (this.showHorizontalScrollBar) {
	    bottomInset = SCROLLBAR_AREA_WIDTH;
	}

	this.viewAreaTop = 1;
	this.viewAreaBottom = bottomInset;
	this.viewAreaLeft = 1;
	this.viewAreaRight = rightInset;

	int cw = getViewAreaWidth();
	if (cw > pd.getWidth()) {
	    pd.setWidth(cw);
	}

	int ch = getViewAreaHeight();
	if (ch > pd.getHeight()) {
	    pd.setHeight(ch);
	}

	setViewContentSize(pd);
	// scrollCheck();
	setViewContentPosition(getViewContentX(), getViewContentY());
    }

    protected void scrollRectToVisible(int x, int y, int width, int height) {
	int viewAreaWidth = getViewAreaWidth();
	int viewAreaHeight = getViewAreaHeight();

	int viewContentX = getViewContentX();
	int viewContentY = getViewContentY();

	int dx = positionAdjustment(viewAreaWidth, x - viewContentX, width);
	int dy = positionAdjustment(viewAreaHeight, y - viewContentY, height);

	if (dx != 0 || dy != 0) {
	    setViewContentPosition(viewContentX - dx, viewContentY - dy);
	}
    }

    private int positionAdjustment(int parentWidth, int childAt, int childWidth) {
	// 変更無し
	if (childAt >= 0 && childWidth + childAt <= parentWidth) {
	    return 0;
	}
	// 変更無し
	if (childAt <= 0 && childWidth + childAt >= parentWidth) {
	    return 0;
	}
	if (childAt > 0 && childWidth <= parentWidth) {
	    return -childAt + parentWidth - childWidth;
	}
	if (childAt >= 0 && childWidth >= parentWidth) {
	    return -childAt;
	}
	if (childAt <= 0 && childWidth <= parentWidth) {
	    return -childAt;
	}
	if (childAt < 0 && childWidth >= parentWidth) {
	    return -childAt + parentWidth - childWidth;
	}
	return 0;
    }

    private void setViewContentSize(Dimension d) {
	this.viewContentWidth = d.getWidth();
	this.viewContentHeight = d.getHeight();

	viewContentSizeChanged();
    }

    /**
     * @see org.gikolet.framework.ui.Widget#firePropertyChanged(java.lang.String)
     */
    protected void firePropertyChanged(String propertyName) {
	if (PROPERTY_SIZE.equals(propertyName)) {
	    revalidateViewContent();
	    repaintViewContent();
	}
	super.firePropertyChanged(propertyName);
    }

    protected void viewContentSizeChanged() {
    }

    public int getViewAreaX() {
	return this.viewAreaLeft;
    }

    public int getViewAreaY() {
	return this.viewAreaTop;
    }

    public int getViewAreaWidth() {
	return getWidth() - this.viewAreaLeft - this.viewAreaRight;
    }

    public int getViewAreaHeight() {
	return getHeight() - this.viewAreaTop - this.viewAreaBottom;
    }

    // forwardがtrueで順方向, falseで逆方向
    public boolean isVScrollEnd(boolean forward) {
	if (forward) {
	    return getViewContentY() + getViewAreaHeight() == getViewContentHeight();
	} else {
	    return getViewContentY() == 0;
	}
    }

    // forwardがtrueで順方向, falseで逆方向
    public boolean isHScrollEnd(boolean forward) {
	if (forward) {
	    return getViewContentX() + getViewAreaWidth() == getViewContentWidth();
	} else {
	    return getViewContentX() == 0;
	}
    }

    public boolean scrollUP() {
	int sw = this.scrollWeight;
	if (sw == -1) {
	    sw = getViewAreaHeight();
	}
	return scrollUP(sw);
    }

    public boolean scrollDown() {
	int sw = this.scrollWeight;
	if (sw == -1) {
	    sw = getViewAreaHeight();
	}
	return scrollDown(sw);
    }

    public boolean scrollLeft() {
	int sw = this.scrollWeight;
	if (sw == -1) {
	    sw = getViewAreaWidth();
	}
	return scrollLeft(sw);
    }

    public boolean scrollRight() {
	int sw = this.scrollWeight;
	if (sw == -1) {
	    sw = getViewAreaWidth();
	}
	return scrollRight(sw);
    }

    public boolean scrollUP(int height) {
	int vy = getViewContentY();
	setViewContentPosition(getViewContentX(), vy - height);
	return vy != getViewContentY();
    }

    public boolean scrollDown(int height) {
	int vy = getViewContentY();
	setViewContentPosition(getViewContentX(), vy + height);
	return vy != getViewContentY();
    }

    public boolean scrollLeft(int width) {
	int vx = getViewContentX();
	setViewContentPosition(vx - width, getViewContentY());
	return vx != getViewContentX();
    }

    public boolean scrollRight(int width) {
	int vx = getViewContentX();
	setViewContentPosition(vx + width, getViewContentY());
	return vx != getViewContentX();
    }

    public int getViewContentX() {
	return this.viewContentX;
    }

    public int getViewContentY() {
	return this.viewContentY;
    }

    public int getViewContentWidth() {
	return this.viewContentWidth;
    }

    public int getViewContentHeight() {
	return this.viewContentHeight;
    }

    public void setViewContentPosition(int x, int y) {
	int viewAreaWidth = getViewAreaWidth();
	int viewAreaHeight = getViewAreaHeight();

	int viewContentWidth = getViewContentWidth();
	int viewContentHeigth = getViewContentHeight();

	if (viewContentWidth < x + viewAreaWidth) {
	    x = Math.max(0, viewContentWidth - viewAreaWidth);
	} else if (x < 0) {
	    x = 0;
	}
	if (viewContentHeigth < y + viewAreaHeight) {
	    y = Math.max(0, viewContentHeigth - viewAreaHeight);
	} else if (y < 0) {
	    y = 0;
	}
	this.viewContentX = x;
	this.viewContentY = y;

	repaint();
    }

    public Dimension getPreferredSize(int hintWidth, int hintHeight) {
	Dimension ps = getContentPreferredSize(DEFAULT_LAYOUT, DEFAULT_LAYOUT);

	int pW = ps.getWidth();
	int pH = ps.getHeight();
	ps.setWidth(ps.getWidth() + 2);
	ps.setHeight(ps.getHeight() + 2);

	// int hsp = getHorizontalScrollBarPolicy();
	// int vsp = getVerticalScrollBarPolicy();

	if (this.vsbPolicy == SCROLLBAR_ALWAYS) {
	    ps.setWidth(1 + pW + SCROLLBAR_AREA_WIDTH);
	} else if (this.vsbPolicy == SCROLLBAR_AS_NEEDED) {
	    if (hintHeight != DEFAULT_LAYOUT) {
		if (ps.getHeight() > hintHeight) {
		    ps.setWidth(1 + pW + SCROLLBAR_AREA_WIDTH);
		}
	    }
	}
	if (this.hsbPolicy == SCROLLBAR_ALWAYS) {
	    ps.setHeight(1 + pH + SCROLLBAR_AREA_WIDTH);
	} else if (this.hsbPolicy == SCROLLBAR_AS_NEEDED) {
	    if (hintWidth != DEFAULT_LAYOUT) {
		if (ps.getWidth() > hintWidth) {
		    ps.setHeight(1 + pH + SCROLLBAR_AREA_WIDTH);

		    if (this.vsbPolicy == SCROLLBAR_AS_NEEDED) {
			if (hintHeight != DEFAULT_LAYOUT) {
			    if (ps.getHeight() > hintHeight) {
				ps.setWidth(1 + pW + SCROLLBAR_AREA_WIDTH);
			    }
			}
		    }
		}
	    }
	}
	if (hintWidth != DEFAULT_LAYOUT) {
	    ps.setWidth(hintWidth - 2);
	}
	if (hintHeight != DEFAULT_LAYOUT) {
	    ps.setHeight(hintHeight - 2);
	}
	return ps;
    }

    public void setVerticalScrollBarPolicy(int policy) {
	switch (policy) {
	    case SCROLLBAR_ALWAYS:
	    case SCROLLBAR_AS_NEEDED:
	    case SCROLLBAR_NEVER:
		break;
	    default:
		throw new IllegalArgumentException("policy is illegal.");
	}
	this.vsbPolicy = policy;
	revalidate();
	repaint();
    }

    public void setHorizontalScrollBarPolicy(int policy) {
	switch (policy) {
	    case SCROLLBAR_ALWAYS:
	    case SCROLLBAR_AS_NEEDED:
	    case SCROLLBAR_NEVER:
		break;
	    default:
		throw new IllegalArgumentException("policy is illegal.");
	}
	this.hsbPolicy = policy;
	revalidate();
	repaint();
    }

    public int getVerticalScrollBarPolicy() {
	return this.vsbPolicy;
    }

    public int getHorizontalScrollBarPolicy() {
	return this.hsbPolicy;
    }
}
